Mars Rover & Heli Demo¶

Binder IPYNB HTML

This tutorial uses data published by NASA:

  • https://mars.nasa.gov/mmgis-maps/M20/Layers/json/M20_waypoints.json
  • https://mars.nasa.gov/mmgis-maps/M20/Layers/json/M20_traverse.json
  • https://mars.nasa.gov/mmgis-maps/M20/Layers/json/m20_heli_waypoints.json
  • https://mars.nasa.gov/mmgis-maps/M20/Layers/json/m20_heli_flight_path.json

Hat tip to https://fosstodon.org/@65dBnoise/108251277108722231 for providing the pointers

Known issues:

  1. MovingPandas will calculate movement speeds based on Earth's WGS84 ellipsoid by default
In [1]:
import numpy as np
import pandas as pd
import geopandas as gpd
import movingpandas as mpd
import shapely as shp
import hvplot.pandas 
import matplotlib.pyplot as plt

from geopandas import GeoDataFrame, read_file
from shapely.geometry import Point, LineString, Polygon
from datetime import datetime, timedelta
from holoviews import opts, dim
from os.path import exists
from urllib.request import urlretrieve

import warnings
warnings.filterwarnings('ignore')

plot_defaults = {'linewidth':5, 'capstyle':'round', 'figsize':(9,3), 'legend':True}
opts.defaults(opts.Overlay(active_tools=['wheel_zoom'], frame_width=500, frame_height=400))
hvplot_defaults = {'tiles':None, 'cmap':'Viridis', 'colorbar':True}

mpd.show_versions()
MovingPandas 0.16.0

SYSTEM INFO
-----------
python     : 3.10.8 | packaged by conda-forge | (main, Nov 22 2022, 08:16:53) [MSC v.1929 64 bit (AMD64)]
executable : H:\miniconda3\envs\mpd-ex\python.exe
machine    : Windows-10-10.0.19045-SP0

GEOS, GDAL, PROJ INFO
---------------------
GEOS       : None
GEOS lib   : None
GDAL       : 3.5.0
GDAL data dir: None
PROJ       : 9.0.0
PROJ data dir: H:\miniconda3\pkgs\proj-9.0.0-h1cfcee9_1\Library\share\proj

PYTHON DEPENDENCIES
-------------------
geopandas  : 0.13.0
pandas     : 2.0.1
fiona      : 1.8.21
numpy      : 1.23.5
shapely    : 1.8.2
rtree      : 1.0.1
pyproj     : 3.3.1
matplotlib : 3.7.1
mapclassify: 2.4.3
geopy      : 2.3.0
holoviews  : 1.14.9
hvplot     : 0.8.3
geoviews   : 1.9.6
stonesoup  : 0.1b12

Loading the rover & heli data¶

"The car-sized Perseverance and its little helicopter buddy Ingenuity landed together inside Mars' Jezero Crater on Feb. 18." https://www.space.com/perseverance-rover-100-mars-days (by Mike Wall published June 02, 2021)

"One sol lasts about 24 hours and 40 minutes, slightly longer than an Earth day." https://www.space.com/perseverance-rover-100-mars-days

In [2]:
def to_timestamp(row):
    start_time = datetime(2021,2,18,0,0,0)  #  sol 0 
    try: 
        sol = row['sol']  # rover
    except KeyError:
        sol = row['Sol']  # heli 
    td = timedelta(hours=24*sol, minutes=40*sol)
    return start_time + td

def get_df_from_url(url):
    file = url.split('/')[-1]
    if not exists(file):
        urlretrieve(url, file)
    gdf = read_file(file)
    gdf['time'] = gdf.apply(to_timestamp, axis=1)
    gdf.set_index('time', inplace=True)
    return gdf

m20_waypoints_json = "https://mars.nasa.gov/mmgis-maps/M20/Layers/json/M20_waypoints.json"
heli_waypoints_json = "https://mars.nasa.gov/mmgis-maps/M20/Layers/json/m20_heli_waypoints.json"
m20_df = get_df_from_url(m20_waypoints_json)
heli_df = get_df_from_url(heli_waypoints_json)
print(f'M20 records: {len(m20_df)}')
print(f'Heli records: {len(heli_df)}')
M20 records: 156
Heli records: 29
In [3]:
m20_df.describe()
Out[3]:
site drive sol SCLK_START SCLK_END easting northing elev_geoid elev_radii radius ... roll pitch yaw yaw_rad tilt dist_m dist_total dist_km dist_mi ORIG_FID
count 156.000000 156.000000 156.000000 1.560000e+02 1.560000e+02 1.560000e+02 1.560000e+02 156.000000 156.000000 1.560000e+02 ... 156.000000 156.000000 156.000000 156.000000 156.000000 156.000000 156.000000 156.000000 156.000000 156.000000
mean 10.705128 1635.076923 261.012821 6.858288e+08 6.858989e+08 4.353919e+06 1.093211e+06 -2565.319896 -4248.434796 3.391942e+06 ... 0.078588 0.877516 -29.754424 -0.519312 3.247422 73.370897 3369.154237 3.369103 2.093141 83.500000
std 7.343391 1757.848735 141.790576 5.665063e+07 5.664626e+07 9.786968e+02 6.396507e+02 14.163160 13.179175 1.317897e+01 ... 3.123551 3.221801 106.107954 1.851936 3.148950 84.848770 3668.565027 3.668456 2.279536 45.177428
min 3.000000 0.000000 13.000000 0.000000e+00 0.000000e+00 4.351710e+06 1.092270e+06 -2585.869629 -4266.522461 3.391923e+06 ... -12.377935 -7.797700 -179.475000 -3.132400 -0.043088 0.000000 0.000000 0.000000 0.000000 6.000000
25% 4.000000 410.000000 126.750000 6.781907e+08 6.781920e+08 4.353956e+06 1.092629e+06 -2574.016500 -4255.397319 3.391935e+06 ... -1.075334 -0.520792 -126.751645 -2.212250 1.087888 8.878500 302.875000 0.305000 0.187500 44.750000
50% 9.000000 1304.000000 330.500000 6.962873e+08 6.962881e+08 4.354411e+06 1.093255e+06 -2569.165000 -4252.637510 3.391937e+06 ... -0.120000 0.531261 -58.070700 -1.013550 1.995700 36.329000 2117.274000 2.120000 1.315000 83.500000
75% 17.250000 2247.000000 395.250000 7.020252e+08 7.020356e+08 4.354547e+06 1.093694e+06 -2558.318000 -4245.486393 3.391945e+06 ... 1.179550 2.214195 45.275105 0.790200 4.457050 111.595500 5586.892750 5.587500 3.475000 122.250000
max 24.000000 9436.000000 437.000000 7.057373e+08 7.057424e+08 4.355183e+06 1.094703e+06 -2520.911000 -4204.339000 3.391986e+06 ... 13.177640 19.349400 179.893300 3.139700 19.833200 313.832000 11445.860000 11.450000 7.110000 161.000000

8 rows × 22 columns

In [4]:
m20_df.hvplot(title="M20 & heli waypoints", hover_cols=['sol'], **hvplot_defaults) * heli_df.hvplot()
Out[4]:
In [5]:
m20_traj = mpd.Trajectory(m20_df, 'm20')
heli_traj = mpd.Trajectory(heli_df, 'heli')
In [6]:
traj_plot = m20_traj.hvplot(title="M20 & heli trajectories", line_width=3, **hvplot_defaults) * heli_traj.hvplot(line_width=3, **hvplot_defaults)
traj_plot 
Out[6]:
In [7]:
m20_traj.hvplot(title="Rover speed (only suitable for relative comparison)", 
                c='speed', line_width=7, **hvplot_defaults) 
Out[7]:
In [8]:
m20_detector = mpd.TrajectoryStopDetector(m20_traj)
stop_points = m20_detector.get_stop_points(min_duration=timedelta(seconds=60), max_diameter=100)
stop_points['duration_days'] = stop_points['duration_s']/(60*60*24)
stop_points.head()
Out[8]:
geometry start_time end_time traj_id duration_s duration_days
stop_id
m20_2021-03-03 08:40:00 POINT (77.45095 18.44463) 2021-03-03 08:40:00 2021-03-05 10:00:00 m20 177600.0 2.055556
m20_2021-03-06 10:40:00 POINT (77.45164 18.44517) 2021-03-06 10:40:00 2021-03-22 21:20:00 m20 1420800.0 16.444444
m20_2021-03-23 22:00:00 POINT (77.45102 18.44487) 2021-03-23 22:00:00 2021-04-08 08:00:00 m20 1332000.0 15.416667
m20_2021-04-09 08:40:00 POINT (77.45225 18.44440) 2021-04-09 08:40:00 2021-05-17 09:20:00 m20 3285600.0 38.027778
m20_2021-05-22 12:40:00 POINT (77.45218 18.44382) 2021-05-22 12:40:00 2021-05-31 18:40:00 m20 799200.0 9.250000
In [9]:
heli_detector = mpd.TrajectoryStopDetector(heli_traj)
heli_stop_points = heli_detector.get_stop_points(min_duration=timedelta(seconds=60), max_diameter=100)
heli_stop_points['duration_days'] = heli_stop_points['duration_s']/(60*60*24)
heli_stop_points.head()
Out[9]:
geometry start_time end_time traj_id duration_s duration_days
stop_id
heli_2021-04-03 04:40:00 POINT (77.45102 18.44486) 2021-04-03 04:40:00 2021-04-29 22:00:00 heli 2308800.0 26.722222
heli_2021-08-04 12:40:00 POINT (77.43916 18.43277) 2021-08-04 12:40:00 2021-10-23 16:40:00 heli 6926400.0 80.166667
heli_2022-03-23 18:40:00 POINT (77.44288 18.45068) 2022-03-23 18:40:00 2022-04-03 01:20:00 heli 888000.0 10.277778
In [10]:
stop_point_plot = stop_points.hvplot(title='M20 & heli stops ', 
                                     geo=True, size=np.log(dim('duration_days'))*10, 
                                     hover_cols=['duration_days'], color='blue', alpha=0.5) 
heli_stop_plot = heli_stop_points.hvplot(geo=True, size=np.log(dim('duration_days'))*10, 
                                     hover_cols=['duration_days'], color='red', alpha=0.5) 
stop_point_plot * heli_stop_plot * traj_plot
Out[10]:

Mars background map¶

Compare to https://mars.nasa.gov/mars2020/mission/where-is-the-rover/

In [11]:
from bokeh.models import TMSTileSource

tile_url = 'http://s3-eu-west-1.amazonaws.com/whereonmars.cartodb.net/celestia_mars-shaded-16k_global/{Z}/{X}/{Y}.png'

def mars_tiles(plot, element):
    plot.state.add_tile(TMSTileSource(url=tile_url), level='underlay')

traj_map = m20_traj.hvplot(title="M20 & heli trajectories", tiles=None) * heli_traj.hvplot(**hvplot_defaults)
traj_map.opts(hooks=[mars_tiles])
Out[11]:

Work in progress:¶

In [12]:
from geoviews.element import WMTS

MarsImagery = WMTS(
    'https://trek.nasa.gov/tiles/Mars/EQ/Mars_MGS_MOLA_ClrShade_merge_global_463m/1.0.0/default/default028mm/{Z}/{Y}/{X}.jpg',
    name="Mars")

m20_traj.hvplot(title="M20 & heli trajectories", tiles=MarsImagery) * heli_traj.hvplot(**hvplot_defaults)
Out[12]:

Continue exploring MovingPandas¶

  1. Bird migration analysis
  2. Ship data analysis
  3. Horse collar data exploration
  4. OSM traces
  5. Soccer game
  6. Mars rover & heli